package com.qing.web_freemarke.aspect;


import com.alibaba.fastjson.JSON;

import com.qing.web_freemarke.entity.WebLogEntity;
import com.qing.web_freemarke.service.weblog.WebLogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;

@Aspect
@Component
@Order(1) //当有多个Aop时按顺序执行，order越小，执行优先级越高
//@Profile({"dev","test"}) //只想在开发环境或者测试环境中使
public class WebLogAspect {
	private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
	//换行符
	private static final String LINE_SEPARATOR = System.lineSeparator();

	@Autowired
	private WebLogService webLogService;

	//以自定义@WebLog 注解为切点
	@Pointcut("@annotation(com.qing.web_freemarke.aspect.WebLog)")
	public void webLog() {
	}


	/**
	 * 在切点之后织入
	 *
	 * @throws Throwable
	 */
	@After("webLog()")
	public void doAfter() throws Throwable {
		// 接口结束后换行，方便分割查看
		logger.info("=========================================== End ===========================================" + LINE_SEPARATOR);
	}

	/**
	 * 环绕
	 *
	 * @param proceedingJoinPoint
	 * @return
	 * @throws Throwable
	 */
	@Around("webLog()")
	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		// 开始打印请求日志
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		// 获取 @WebLog 注解的描述信息
		String methodDescription = getAspectLogDescription(proceedingJoinPoint);

		// 打印请求相关参数
		logger.info("========================================== Start ==========================================");
		// 打印请求 url
		logger.info("URL            : {}", request.getRequestURL().toString());
		// 打印描述信息
		logger.info("Description    : {}", methodDescription);
		// 打印 Http method
		logger.info("HTTP Method    : {}", request.getMethod());
		// 打印调用 controller 的全路径以及执行方法
		logger.info("Class Method   : {}.{}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
		// 打印请求的 IP
		logger.info("IP             : {}", request.getRemoteAddr());
		// 打印请求入参
		logger.info("Request Args   : {}", getRequestParams(proceedingJoinPoint)); // JSON.toJSONString(proceedingJoinPoint.getArgs())


		long startTime = System.currentTimeMillis();
		Object result = proceedingJoinPoint.proceed();
		// 打印出参

		if (JSON.toJSONString(result).length() > 500) {
			logger.info("Response Args Size : {}", JSON.toJSONString(result).length());
		} else {
			logger.info("Response Args  : {}", JSON.toJSONString(result));
		}

		// 执行耗时
		logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);


		WebLogEntity webLogEntity = new WebLogEntity();
		webLogEntity.setUrl(request.getRequestURL().toString());
		webLogEntity.setDescriptions(methodDescription);
		webLogEntity.setHttpMethod(request.getMethod());
		webLogEntity.setClassMethod(proceedingJoinPoint.getSignature().getDeclaringTypeName() + "." + proceedingJoinPoint.getSignature().getName());
		webLogEntity.setIp(request.getRemoteAddr());
		webLogEntity.setRequestArgs(getRequestParams(proceedingJoinPoint));
		if (JSON.toJSONString(result).length() > 500) {
			webLogEntity.setResponseArgs(String.valueOf(JSON.toJSONString(result).length()));
		} else {
			webLogEntity.setResponseArgs(JSON.toJSONString(result));
		}
		webLogEntity.setLastModifyDate(new Date());
		webLogEntity.setTimeConsuming((int) (System.currentTimeMillis() - startTime));
		webLogService.save(webLogEntity);
		return result;
	}


	/**
	 * 获取切面注解的描述
	 *
	 * @param joinPoint 切点
	 * @return 描述信息
	 * @throws Exception
	 */
	public String getAspectLogDescription(JoinPoint joinPoint)
			throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		StringBuilder description = new StringBuilder("");
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length) {
					description.append(method.getAnnotation(WebLog.class).description());
					break;
				}
			}
		}
		return description.toString();
	}

	private String getRequestParams(ProceedingJoinPoint proceedingJoinPoint)
			throws ClassNotFoundException, IOException {
		/* 类名 */
		String targetObject = proceedingJoinPoint.getTarget().getClass().getName();
		/* 方法名 */
		String methodName = proceedingJoinPoint.getSignature().getName();


		Object[] args = proceedingJoinPoint.getArgs();

		Class<?> targetClass = Class.forName(targetObject);

		Method[] methods = targetClass.getMethods();

		StringBuilder requestBuilder = new StringBuilder(0);

		/**
		 * 遍历方法 获取能与方法名相同且请求参数个数也相同的方法
		 */
		for (Method method : methods) {
			if (!method.getName().equals(methodName)) {
				continue;
			}

			Class<?>[] classes = method.getParameterTypes();

			if (classes.length != args.length) {
				continue;
			}

			for (int index = 0; index < classes.length; index++) {
				// 如果参数类型是请求和响应的http，则不需要拼接【这两个参数，使用JSON.toJSONString()转换会抛异常】
				if (args[index] instanceof HttpServletRequest
						|| args[index] instanceof HttpServletResponse || args[index] instanceof Model) {
					continue;
				}

				if (Objects.nonNull(args[index])) {
					requestBuilder.append(JSON.toJSONString(args[index]));
				}
			}
		}

		return requestBuilder.toString();
	}

}
